package org.hawk.gwt.ppc.scanner.utils;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;

import org.hawk.gwt.ppc.reflect.lang.ClassDelegate;
import org.hawk.gwt.ppc.reflect.lang.LangClassLoader;
import org.hawk.gwt.ppc.reflect.lang.SystemClassLoader;
import org.hawk.gwt.ppc.scanner.SourcesLoader;
import org.hawk.gwt.ppc.utils.Name;
/**
 * Represents class-level java scope.
 * @author alex.bereznevatiy@gmail.com
 */
public class JavaTypeScope extends JavaScope {
	private static final Map<String,String> wrappers;
	static{
		Map<String,String> m = new HashMap<String, String>();
		for(Class<?> cls : ClassDelegate.WRAPPERS.keySet()){
			m.put(cls.getName(), ClassDelegate.WRAPPERS.get(cls).getName());
		}
		wrappers = Collections.unmodifiableMap(m);
	}
	
	private static final Map<String,String> unwrappers;
	static{
		Map<String,String> m = new HashMap<String, String>();
		for(Class<?> cls : ClassDelegate.WRAPPERS.keySet()){
			m.put(ClassDelegate.WRAPPERS.get(cls).getName(),cls.getName());
		}
		unwrappers = Collections.unmodifiableMap(m);
	}
	
	
	private Map<String,String> imports;
	private String currentPackage;
	private SourcesLoader loader;
	private String superClass;
	private List<String> implementedInterfaces;
	private String className;
	private String entityType;

	JavaTypeScope(JavaScope parent, int start, 
			List<JavaVariable> vars,SourcesLoader loader) {
		super(parent, start, vars);
		this.imports = new HashMap<String, String>();
		this.loader = loader;
		this.implementedInterfaces = new LinkedList<String>();
	}
	
	void setCurrentPackage(String currentPackage){
		this.currentPackage = currentPackage;
	}

	void addImport(String name) {
		imports.put(Name.getSimpleName(name), name);
	}
	
	void setExtendedClass(String name){
		if(name.indexOf('.')<0){
			name = Name.getChildJavaName(currentPackage, name);
		}
		superClass = name;
	}
	
	void setClassName(String name){
		className = name;
	}
	
	String getClassName(){
		return className;
	}
	
	void addImplementedInterface(String name){
		implementedInterfaces.add(name);
	}

	void setEntityType(String entityType) {
		this.entityType = entityType.intern();
	}
	/**
	 * @return keyword that represents entity type
	 * of this entity. The valid results are:
	 * <ul>
	 * <li>class
	 * <li>interface
	 * <li>enum
	 * <li>@interface
	 * </ul>
	 */
	public String getEntityType(){
		if(entityType==null){
			return JavaClassDescriptor.CLASS_KEYWORD;
		}
		return entityType;
	}
	/**
	 * @return <code>true</code> if this type is an interface
	 */
	public boolean isInterface(){
		return getEntityType()==JavaClassDescriptor.INTERFACE_KEYWORD;
	}
	/**
	 * @return <code>true</code> if this type is a class
	 */
	public boolean isClass(){
		return getEntityType()==JavaClassDescriptor.CLASS_KEYWORD;
	}
	/**
	 * @return <code>true</code> if this type is enum
	 */
	public boolean isEnum(){
		return getEntityType()==JavaClassDescriptor.ENUM_KEYWORD;
	}
	/**
	 * @return <code>true</code> if this type is annotation
	 */
	public boolean isAnnotation(){
		return getEntityType()==JavaClassDescriptor.ANNOTATION_KEYWORD;
	}

	@Override
	public List<JavaVariable> getVariables(int index) {
		List<JavaVariable> result = new LinkedList<JavaVariable>();
		result.addAll(getSuperClass().getVariables(index));
		for(JavaTypeScope iface : getImplementedInterfaces()){
			result.addAll(iface.getVariables(index));
		}
		if(getParent()!=null){
			result.addAll(getParent().getVariables(index));
		}
		for(JavaVariable var : vars){
			result.add(var);
		}
		return Collections.unmodifiableList(result);
	}

	protected List<JavaTypeScope> getImplementedInterfaces() {
		List<JavaTypeScope> result = new LinkedList<JavaTypeScope>();
		for(String iface : implementedInterfaces){
			result.add(resolveTypeDescriptor(iface).getCompilationUnit());
		}
		return Collections.unmodifiableList(result);
	}

	protected JavaClassDescriptor resolveTypeDescriptor(String type) {
		type = resolveType(type);
		try {
			return JavaClassDescriptorUtils.getClassDescriptor(
				type, loader);
		}
		catch(Exception ex){}
		try{
			return new JavaClassDescriptor(findClass(type));
		}
		catch(ClassNotFoundException ex){
			return null;
		}
	}

	private Class<?> findClass(String type) throws ClassNotFoundException {
		if(SystemClassLoader.isPrimitiveClass(type)){
			return SystemClassLoader.getPrimitiveClass(type);
		}
		return Class.forName(type);
	}

	protected JavaTypeScope getSuperClass() {
		if(superClass==null||resolveType(superClass)
				.equals(Object.class.getName())){
			return ObjectTypeScope.INSTANCE;
		}
		return resolveTypeDescriptor(superClass).getCompilationUnit();
	}

	@Override
	public JavaVariable getVariable(int index, String name) {
		List<JavaVariable> vars = getVariables(index);
		ListIterator<JavaVariable> iterator = vars.listIterator(vars.size());
		while(iterator.hasPrevious()){
			JavaVariable var = iterator.previous();
			if(var.getName().equals(name)){
				return var;
			}
		}
		return null;
	}

	public List<JavaMethod> getMethods() {
		List<JavaMethod> result = new ArrayList<JavaMethod>();
		for(JavaScope child : getChildren()){
			if(child instanceof JavaMethod){
				result.add((JavaMethod)child);
			}
		}
		result.addAll(getSuperClass().getMethods());
		for(JavaTypeScope iface : getImplementedInterfaces()){
			result.addAll(iface.getMethods());
		}
		return Collections.unmodifiableList(result);
	}

	public JavaMethod getMethod(String name, String...parametersTypes) {
		for(JavaMethod m : getMethods()){
			if(m.matches(name,parametersTypes)){
				return m;
			}
		}
		JavaScope parent = getParent();
		while(parent!=null){
			if(parent instanceof JavaTypeScope){
				return ((JavaTypeScope)parent).getMethod(name, parametersTypes);
			}
			parent = getParent();
		}
		return null;
	}

	@Override
	protected String resolveType(String typeSimpleName) {
		String rawName = Name.getRawName(typeSimpleName);
		String typeParams = Name.getTypeParameters(typeSimpleName);
		if(rawName.indexOf('.')>=0){
			return typeSimpleName;
		}
		if(imports.get(rawName)!=null){
			return imports.get(rawName)+typeParams;
		}
		String pkgPrivateName = Name.getChildJavaName(
				currentPackage, rawName);
		if(loader!=null&&loader.isClassExists(pkgPrivateName)){
			return pkgPrivateName+typeParams;
		}
		if(LangClassLoader.isLangClass(rawName)){
			return Name.getChildJavaName(
				LangClassLoader.LANG_PACKAGE, typeSimpleName);
		}
		return super.resolveType(typeSimpleName);
	}

	@Override
	public boolean equals(Object obj) {
		if(this==obj)return true;
		if(obj instanceof JavaTypeScope){
			JavaTypeScope type = (JavaTypeScope)obj;
			return type.className.equals(className);
		}
		return super.equals(obj);
	}

	@Override
	public String toString() {
		return "["+getClass().getSimpleName()+']'+className;
	}

	public boolean isAssignableFrom(JavaTypeScope type) {
		JavaTypeScope compare = this;
		if(equals(type)){
			return true;
		}
		while(compare!=null&&type!=null){
			if(type.equals(compare)){
				return true;
			}
			compare = compare.getSuperClass();
		}
		if(wrappers.containsKey(className)){
			return wrappers.get(className).equals(type.className);
		}
		if(unwrappers.containsKey(className)){
			return unwrappers.get(className).equals(type.className);
		}
		return false;
	}
}
